package xyhoo.springframework.beans;


import kotlin.jvm.JvmClassMappingKt;
import kotlin.reflect.KFunction;
import kotlin.reflect.KParameter;
import kotlin.reflect.full.KClasses;
import kotlin.reflect.jvm.KCallablesJvm;
import kotlin.reflect.jvm.ReflectJvmMapping;

import xyhoo.springframework.core.KotlinDetector;
import xyhoo.springframework.util.*;

import java.beans.ConstructorProperties;
import java.beans.PropertyDescriptor;
import java.beans.PropertyEditor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URL;
import java.time.temporal.Temporal;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;



public abstract class BeanUtils {

    private static final Map<Class<?>, Object> DEFAULT_TYPE_VALUES = Map.of(
            boolean.class, false,
            byte.class, (byte) 0,
            short.class, (short) 0,
            int.class, 0,
            long.class, 0L,
            float.class, 0F,
            double.class, 0D,
            char.class, '\0');

    public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
        Assert.notNull(clazz, "Class must not be null");
        if (clazz.isInterface()) {
            throw new BeanInstantiationException(clazz, "Specified class is an interface");
        }
        Constructor<T> ctor;
        try {
            ctor = clazz.getDeclaredConstructor();
        }
        catch (NoSuchMethodException ex) {
            ctor = findPrimaryConstructor(clazz);
            if (ctor == null) {
                throw new BeanInstantiationException(clazz, "No default constructor found", ex);
            }
        }
        catch (LinkageError err) {
            throw new BeanInstantiationException(clazz, "Unresolvable class definition", err);
        }
        return instantiateClass(ctor);
    }

    public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
        Assert.notNull(clazz, "Class must not be null");
        if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(clazz)) {
            return KotlinDelegate.findPrimaryConstructor(clazz);
        }
        return null;
    }

    public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
        Assert.notNull(ctor, "Constructor must not be null");
        try {
            ReflectionUtils.makeAccessible(ctor);
            if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
                return KotlinDelegate.instantiateClass(ctor, args);
            }
            else {
                int parameterCount = ctor.getParameterCount();
                Assert.isTrue(args.length <= parameterCount, "Can't specify more arguments than constructor parameters");
                if (parameterCount == 0) {
                    return ctor.newInstance();
                }
                Class<?>[] parameterTypes = ctor.getParameterTypes();
                Object[] argsWithDefaultValues = new Object[args.length];
                for (int i = 0 ; i < args.length; i++) {
                    if (args[i] == null) {
                        Class<?> parameterType = parameterTypes[i];
                        argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null);
                    }
                    else {
                        argsWithDefaultValues[i] = args[i];
                    }
                }
                return ctor.newInstance(argsWithDefaultValues);
            }
        }
        catch (InstantiationException ex) {
            throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
        }
        catch (IllegalAccessException ex) {
            throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
        }
        catch (IllegalArgumentException ex) {
            throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
        }
        catch (InvocationTargetException ex) {
            throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
        }
    }


    /**
     * Inner class to avoid a hard dependency on Kotlin at runtime.
     */
    private static class KotlinDelegate {

        /**
         * Retrieve the Java constructor corresponding to the Kotlin primary constructor, if any.
         * @param clazz the {@link Class} of the Kotlin class
         * @see <a href="https://kotlinlang.org/docs/reference/classes.html#constructors">
         * https://kotlinlang.org/docs/reference/classes.html#constructors</a>
         */
        public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
            try {
                KFunction<T> primaryCtor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(clazz));
                if (primaryCtor == null) {
                    return null;
                }
                Constructor<T> constructor = ReflectJvmMapping.getJavaConstructor(primaryCtor);
                if (constructor == null) {
                    throw new IllegalStateException(
                            "Failed to find Java constructor for Kotlin primary constructor: " + clazz.getName());
                }
                return constructor;
            }
            catch (UnsupportedOperationException ex) {
                return null;
            }
        }

        /**
         * Instantiate a Kotlin class using the provided constructor.
         * @param ctor the constructor of the Kotlin class to instantiate
         * @param args the constructor arguments to apply
         * (use {@code null} for unspecified parameter if needed)
         */
        public static <T> T instantiateClass(Constructor<T> ctor, Object... args)
                throws IllegalAccessException, InvocationTargetException, InstantiationException {

            KFunction<T> kotlinConstructor = ReflectJvmMapping.getKotlinFunction(ctor);
            if (kotlinConstructor == null) {
                return ctor.newInstance(args);
            }

            if ((!Modifier.isPublic(ctor.getModifiers()) || !Modifier.isPublic(ctor.getDeclaringClass().getModifiers()))) {
                KCallablesJvm.setAccessible(kotlinConstructor, true);
            }

            List<KParameter> parameters = kotlinConstructor.getParameters();

            Assert.isTrue(args.length <= parameters.size(),
                    "Number of provided arguments must be less than or equal to the number of constructor parameters");
            if (parameters.isEmpty()) {
                return kotlinConstructor.call();
            }
            Map<KParameter, Object> argParameters = CollectionUtils.newHashMap(parameters.size());
            for (int i = 0 ; i < args.length ; i++) {
                if (!(parameters.get(i).isOptional() && args[i] == null)) {
                    argParameters.put(parameters.get(i), args[i]);
                }
            }
            return kotlinConstructor.callBy(argParameters);
        }
    }

}
